package com.uinnova.product.eam.web.config.mvc;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.binary.core.lang.StringUtils;
import com.uinnova.product.eam.config.FilterUtil;
import com.uinnova.project.base.diagram.comm.constant.CommonConst;
import com.uinnova.project.base.diagram.enums.LoginMethodEnum;
import com.uinnova.project.base.diagram.util.RedisUtil;
import com.uino.api.client.permission.IOauthApiSvc;
import com.uino.bean.permission.base.OauthResourceDetail;
import com.uino.bean.permission.base.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * 对接数字空间sso
 *
 * @author lichong
 * @since 2022/6/6 13:43
 */
@WebFilter(urlPatterns = "/*")
@Component
@Order(1)
@Slf4j
@ConditionalOnProperty(name = "monet.login.loginMethod", havingValue = "sso")
public class UINOSSOLoginFilter implements Filter {

    @Autowired
    private RedisUtil redisUtil;

    @Value("${monet.login.loginMethod}")
    private String loginMethod;

    @Autowired
    IOauthApiSvc oauthApiSvc;

    @Autowired
    FilterUtil filterUtil;

    @Value("${server.servlet.context-path:/}")
    private String contextPath;

    @Value("${wiki.oauth.server.url}")
    private String oauthServerUrl;

    @Value("${wiki.oauth.client.id}")
    private String CLIENT_ID;

    @Value("${local.oauth.client.id}")
    private String LOCAL_CLIENT_ID;

    @Value("${wiki.oauth.client.user_agent}")
    private String USER_AGENT;

    @Value("${local.oauth.client.user_agent}")
    private String LOCAL_USER_AGENT;

    @Value("${wiki.oauth.server.token_callback.url}")
    private String callbackUrl;

    {
        log.info("数字空间登录拦截器注册成功");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse rep, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) rep;
        //接口过滤
        String requestUri = request.getRequestURI();
        requestUri = StrUtil.sub(requestUri, requestUri.indexOf(contextPath) + contextPath.length(), requestUri.length());
        OauthResourceDetail resourceDetail = oauthApiSvc.getResourceDetail(contextPath.replace("/",""));
        List<String> permitAllUrls = resourceDetail.getPermitAllUrls();
        String finalRequestUri = requestUri;
        boolean isPermitFlag = permitAllUrls
                .stream()
                .anyMatch(permitUrl -> ReUtil.isMatch(permitUrl.trim(), finalRequestUri));
        //如果以数字空间方式、并且不包含在过滤列表中，则需要进行校验
        if (!LoginMethodEnum.TOKEN.getValue().equals(loginMethod) && !isPermitFlag) {
            String goPageUrl = request.getParameter("goPageUrl");
            String referer = request.getHeader(Header.REFERER.getValue());
            String userAgent = request.getHeader(Header.USER_AGENT.getValue());
            String authorization = request.getHeader(CommonConst.SSO_Auth);
            boolean isLocal = false;
            boolean isMonet = true;
            // 从请求头SSO-Authorization获取,从请求头AccToken获取
            authorization = StringUtils.isEmpty(authorization) ? request.getHeader(CommonConst.ACC_TOKEN) : authorization;
            if (StringUtils.isEmpty(authorization)) {
                authorization = request.getHeader(Header.AUTHORIZATION.getValue());
            } else {
                isMonet = false;
                authorization = authorization.startsWith("Bearer") ? authorization : "Bearer " + authorization.trim();
            }

            if (!StringUtils.isEmpty(referer) && referer.contains("localhost")) {
                isLocal = true;
            }

            if (isMonet && (StringUtils.isEmpty(authorization) || redisUtil.get(authorization) == null || redisUtil.getExpire(authorization) < CommonConst.LOCAL_TOKEN_REFRESH_TIME)) {
                log.info("authorization为空或不存在对应值，authorization={}, requestUri={}", authorization, request.getRequestURI());
                redirectForAuth(request, response, goPageUrl, isLocal, requestUri);
            } else {
                String agent = isMonet ? (isLocal ? LOCAL_USER_AGENT : USER_AGENT) : userAgent;
                // 判断appName是否为空，不为空使用appName作为agent
                if (!StringUtils.isEmpty(request.getHeader(CommonConst.APP_NAME))) {
                    agent = request.getHeader(CommonConst.APP_NAME);
                }
                HttpResponse userInfoRes = HttpRequest
                        .get(this.oauthServerUrl + "/api/user/userinfo")
                        .contentType(ContentType.JSON.getValue())
                        .auth(authorization)
                        .header(Header.USER_AGENT, agent)
                        .execute();
                if (!userInfoRes.isOk()) {
                    log.info("认证失败的所有信息：authorization={},USER_AGENT={}", authorization, agent);
                    redirectForAuth(request, response, goPageUrl, isLocal, requestUri);
                } else {
                    Object userObj = redisUtil.get(authorization);
                    if (userObj == null) {
                        SysUser currUser = filterUtil.getUserByWikiRes(userInfoRes.body());
                        if (currUser == null) {
                            log.info("数字空间校验成功，获取本地用户信息失败");
                            redirectForAuth(request, response, goPageUrl, isLocal, requestUri);
                        }
                        redisUtil.set(authorization, currUser, CommonConst.LOCAL_TOKEN_EXPIRE_TIME);
                    }
                    chain.doFilter(req, rep);
                }
            }
        } else {
            chain.doFilter(req, rep);
        }
    }

    /**
     * @Author wang
     * @Description 调用数字空间接口校验cookie是否有效
     * @Date 16:50 2021/8/31
     * @Param [request, cookie]
     **/
    private void redirectForAuth(HttpServletRequest request, HttpServletResponse response, String goPageUrl, boolean isLocal, String requestUri) throws ServletException, IOException {
        if (request.getRequestURI().contains("getLoginStatus")) {
            String state = RandomUtil.randomString(10);
            redisUtil.set(state, goPageUrl, CommonConst.LOCAL_TOKEN_EXPIRE_TIME);
            String url = this.oauthServerUrl + "/oauth/authorize?" +
                    "response_type=code" +
                    "&client_id=" + (isLocal ? LOCAL_CLIENT_ID : CLIENT_ID) +
                    "&redirect_uri=" + callbackUrl +
                    "&state=" + state;
            request.setAttribute("url", url);
        }
        request.setAttribute("requestUri", requestUri);
        request.getRequestDispatcher("/wiki/authRedirect").forward(request, response);
    }
}
